Skip to content

fix: Judge an edit by what it changes, not the whole post - #37

Draft
ecv wants to merge 2 commits into
mainfrom
fix/op-gate-no-worse
Draft

fix: Judge an edit by what it changes, not the whole post#37
ecv wants to merge 2 commits into
mainfrom
fix/op-gate-no-worse

Conversation

@ecv

@ecv ecv commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

The convention gate measured a whole opening post every time anyone edited one, so a post written before the convention, or written by a colleague, could not be touched until someone rewrote it to the current bar.

People routed around it with a raw API call instead, which taught the habit of skipping the check rather than meeting it.

The gate now judges the change: a new post still has to meet the bar outright, and an edit passes as long as it leaves the post no further from the bar than it found it.

Ticking a box someone else wrote passes. Adding an em dash to their post does not.

Test plan

  • Ticking a box on a post that already misses the bar goes through
  • An edit that adds a banned phrase or an extra checkbox is still refused
  • A new post that misses the bar is still refused
  • An unreadable posted body lets the edit through and says so

Related to #35

ecv added 2 commits August 14, 2026 17:15
The gate measured the whole body on every call, so editing an opening post
written before the convention, or written by someone else, was refused for
misses that were already there. The only way through was a raw API call
that skipped the check, which taught everyone to route around the gate.

A create is still measured against an empty baseline, so it has to meet the
bar outright. An edit fetches the body already posted, scores both bodies
on the same nine dimensions, and is refused only where the new body's
excess over a rule exceeds the old body's. Equal passes. Better passes.

Key changes:
- Body analysis emits tab-separated counts instead of prose, and a second
  awk pass turns the two count sets into violations
- The live body comes from `gh pr|issue view`, resolved from a number, a
  URL, a branch, or the current branch, with the repo and any leading `cd`
  carried over from the command
- A failed or unresolvable fetch allows the edit and says so, listing what
  the outgoing body misses, rather than falling back to the whole-body
  measure that caused the problem
- A body that cannot be measured on all nine dimensions denies rather than
  passing unchecked
- An edit carrying no body, such as a label or title change, is never
  measured against body rules
The skill told writers to apply a state update through a raw API call when
the gate refused a body whose misses predate their change. The gate now
passes that edit on its own, so the bypass has no remaining use and the
instruction would only teach the habit it was working around.

Describes what the gate now does on an edit, what happens when it cannot
read the posted body, and that an edit touching only labels or a title is
never measured against body rules.
@ecv

ecv commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Design notes and the evidence, since the description carries none of it.

Create and edit come apart at the regex that already matched the command, so nothing new had to learn the difference. A create is measured against an empty baseline, which reproduces the old bar exactly: every rule's excess starts at zero, so any miss is a regression against it. One code path now serves both cases.

The baseline for an edit is the live body, fetched with gh pr view or gh issue view. The selector is whatever the command handed to edit: a number, a URL, a branch name, or nothing at all, in which case gh resolves the current branch exactly as the edit itself would. A leading cd and any --repo flag carry over. Quoted arguments are blanked before tokenising, so a title with spaces in it cannot be mistaken for the selector. Verified against eight command shapes.

A failed fetch fails open, deliberately. Refusing an edit because the network is down would rebuild the wall this issue is about, and a hook cannot tell an offline laptop from a deleted issue. It is not a silent pass: the hook still measures the outgoing body and reports what it misses as advisory context, so the author sees the list and fixes whatever their own edit introduced. The call is bounded at ten seconds where timeout exists.

Both bodies go through the same measuring function, which now emits tab-separated counts on nine dimensions rather than prose. Blocking compares each dimension's excess over its cap, not the raw count, so a summary going from three to four sentences passes while four to five is refused, and so is six to seven. If either body fails to produce all nine records the gate denies rather than passing unchecked.

An edit carrying no body never reaches the comparison. Body extraction runs first and an empty result exits before any network call, so a label or milestone change costs nothing and is never measured against body rules.

Authorship gets no check of its own, and I think adding one would be a mistake. The no-worse rule already covers the case: nobody has to fix what they did not write, and nobody can degrade what someone else did. An author check would cost another API call, would let an author quietly degrade their own post, and would still refuse a stranger's mechanical edit. Who wrote the post is not the question worth asking. Whether this edit hurts it is.

The sharpest evidence is this issue itself. Its own body scores six summary sentences, so under the old gate ticking one of its acceptance criteria was refused. Against the live body fetched from GitHub, the new gate passes that tick and still refuses the same edit once a Closes keyword is added to it.

Eleven cases run against the new hook, all as expected:

  • Create with a body missing the bar: refused. Create with a clean body: allowed. Both messages are byte-identical to the old gate's.
  • Tick a box on a live body that already misses the bar: allowed.
  • Improve a bad live body without reaching the bar: allowed.
  • Add a sixth checkbox to a five-checkbox live body: refused, naming the count it grew from and to.
  • Add an em dash and a banned word: refused on both dimensions.
  • Point a bad body at a clean live one: refused on all three dimensions it degrades.
  • Label-only and title-only edits: allowed, with no fetch attempted.
  • Fetch failure on an edit: allowed, with the disclosure and the unverified miss list.
  • A body the gate cannot fully measure: refused, naming the gate failure.

Both awk programs were re-run under mawk as well as the system awk. No length(array), and every reader of the tab-separated counts sets -F'\t'.

Left out on purpose: no test script is committed. The repo has no test harness or CI to run one, and an honest one needs a stubbed gh on PATH. The suite lives out of tree.

One thing this does not close: the same bypass instruction is written into the infra repository's own guidance, which will need the matching edit once this lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant